package com.sifou.courses.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sifou.courses.Constant;
import com.sifou.courses.cache.RedissonPlus;
import com.sifou.courses.dto.SeckillGoodsDto;
import com.sifou.courses.limit.RateLimit;
import com.sifou.courses.repository.entity.TSeckillGoods;
import com.sifou.courses.repository.mapper.TSeckillGoodsMapper;
import com.sifou.courses.service.IGoodsService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author liuzhongxu
 * @date 2020/8/11
 */
@SuppressWarnings("AlibabaRemoveCommentedCode")
@Slf4j
@Service
public class GoodsServiceImpl implements IGoodsService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedissonPlus redissonPlus;

    @Resource
    private TSeckillGoodsMapper goodsMapper;

    private final String GOODS_INFO = "goods_info";
    private final String GOODS_INFO_ALL = "goods_info_all";
    private final String GOODS_INFO_PAGE = "goods_info_page";

    private final Long TTL = 1000 * 1L;

    @Override
    public void initCache() {
        log.info("秒杀商品预热============start");
        redissonClient.getKeys().flushall();
        log.debug("清空缓存");
        Map<String, Object> buckets = new HashMap<>();
        List<SeckillGoodsDto> goodsDtoList = getGoodsComplex(1, 20);
        goodsDtoList.forEach(goodsDto->{
            buckets.put(Constant.PREFIX_COUNT + goodsDto.getId(), goodsDto.getCount());
            buckets.put(Constant.PREFIX_SALE + goodsDto.getId(), goodsDto.getSale());
            buckets.put(Constant.PREFIX_VERSION + goodsDto.getId(), goodsDto.getVersion());
            redissonClient.getBuckets().set(buckets);
            log.info("预热秒杀商品:" + goodsDto);
        });

        log.info("秒杀商品预热============end");
    }

    @RateLimit(limitType = RateLimit.LimitType.single, limitNum = 5)
    @Override
    public SeckillGoodsDto getGoodsById2() throws InterruptedException {
        String cacheKey = GOODS_INFO + "_2";
        // 返回缓存对象的操作实例，并未发送指令
        RBucket<SeckillGoodsDto> goodsDtoRBucket = redissonPlus.getBucket(cacheKey);
        // 发送get 请求商品缓存
        SeckillGoodsDto goodsDto = goodsDtoRBucket.get();
        return goodsDto;
    }

    @Override
    public SeckillGoodsDto getGoodsById(Integer id) {
        String cacheKey = GOODS_INFO + "_" + id;
        // 返回缓存对象的操作实例，并未发送指令
        RBucket<SeckillGoodsDto> goodsDtoRBucket = redissonPlus.getBucket(cacheKey);
        // 发送get 请求商品缓存
        SeckillGoodsDto goodsDto = goodsDtoRBucket.get();
        if (!ObjectUtils.isEmpty(goodsDto)) {
            log.debug("命中缓存 cacheKey={} value={}", cacheKey, goodsDto);
            return goodsDto;
        }
        log.debug("未命中缓存 重新加载商品数据 cacheKey={}", cacheKey);
        TSeckillGoods tSeckillGoods = goodsMapper.selectById(id);
        goodsDto = new SeckillGoodsDto();
        // dto 面向业务，面向接口
        BeanUtils.copyProperties(tSeckillGoods, goodsDto);
        // 重建缓存，发送set
        goodsDtoRBucket.set(goodsDto, TTL, TimeUnit.MILLISECONDS);
        log.debug("重建缓存 cacheKey={} value={}", cacheKey, goodsDto);
        return goodsDto;
    }

    @Override
    public List<SeckillGoodsDto> getGoodsByIds(List<Integer> ids) {
        List<String> keys = new ArrayList<>();
        /**
         * 构造批量查询缓存key
         */
        ids.forEach(id -> {
            keys.add(GOODS_INFO + "_" + id);
        });
        List<SeckillGoodsDto> goodsDtoList = new ArrayList<>();

        /**
         * 1. mget 如果给定的字符串键里面， 有某个键不存在， 那么这个键的值将以特殊值 nil 表示。
         * 2. getBuckets().get()会过滤掉没有查到的键
         */
        log.debug("批量查询商品信息 cacheKey={}", keys);
        Map<String, SeckillGoodsDto> buckets = redissonClient.getBuckets().get(keys.toArray(new String[ids.size()]));
        log.debug("组装商品数据========start");
        ids.forEach(id->{
            SeckillGoodsDto goodsDto;
            String cacheKey = GOODS_INFO + "_" + id;
            if (buckets.containsKey(cacheKey)) {
                goodsDto = buckets.get(cacheKey);
                log.debug("命中缓存 cacheKey={} value={}", cacheKey, goodsDto);
            } else {
                // 补充没有查到的数据
                goodsDto = getGoodsById(id);
            }
            goodsDtoList.add(goodsDto);
        });
        log.debug("组装商品数据========end");

        return goodsDtoList;
    }

    @Override
    public List<SeckillGoodsDto> getGoodsNormal(int page, int size) {
        IPage<TSeckillGoods> goodsPage = new Page<>(page, size);
        String cacheKey = GOODS_INFO_PAGE + "_" + goodsPage.cacheKey();
        RBucket<List<SeckillGoodsDto>> listRBucket = redissonPlus.getBucket(cacheKey);
        List<SeckillGoodsDto> goodsDtoList = listRBucket.get();
        if (!ObjectUtils.isEmpty(goodsDtoList)) {
            log.debug("命中缓存 cacheKey={} value={}", cacheKey, goodsDtoList);
            return goodsDtoList;
        }
        log.debug("未命中缓存 cacheKey={} 重新加载商品列表数据", cacheKey);
        goodsPage = goodsMapper.selectPage(goodsPage, Wrappers.<TSeckillGoods>lambdaQuery().orderByDesc(TSeckillGoods::getId));
        List<TSeckillGoods> goodsList = goodsPage.getRecords();
        List<SeckillGoodsDto> finalgoodsDtoList = new ArrayList<>();
        goodsList.forEach(tSeckillGoods -> {
            SeckillGoodsDto goodsDto = new SeckillGoodsDto();
            BeanUtils.copyProperties(tSeckillGoods, goodsDto);
            finalgoodsDtoList.add(goodsDto);
        });
        listRBucket.set(finalgoodsDtoList, TTL, TimeUnit.MILLISECONDS);
        log.debug("重建缓存 cacheKey={} value={}", cacheKey, finalgoodsDtoList);
        return finalgoodsDtoList;
    }

    @Override
    public List<SeckillGoodsDto> getGoodsComplex(int page, int size) {
        RList<Integer> goodsIdsRList = redissonClient.getList(GOODS_INFO_ALL);
        int offset = page > 0 ? (page - 1) * size : 0;

        if (goodsIdsRList.isExists()) {
            int total = goodsIdsRList.size();
            if (offset > total) {
                // 没数据
                return new ArrayList<>();
            }
            List<Integer> goodsIds = goodsIdsRList.range(offset, size);
            log.debug("商品列表缓存 cacheKey={} value={}", GOODS_INFO_ALL, goodsIds);
            return getGoodsByIds(goodsIds);
        }
        log.debug("未命中缓存 cacheKey={} 重新加载商品列表数据" + GOODS_INFO_ALL);
        List<TSeckillGoods> goodsList = goodsMapper.selectList(Wrappers.<TSeckillGoods>lambdaQuery()
                .select(TSeckillGoods.class, goods -> goods.getColumn().equals("id"))
                .orderByDesc(TSeckillGoods::getId));
        List<Integer> allGoodsIds = goodsList.stream().map(TSeckillGoods::getId).collect(Collectors.toList());
        log.debug("全部商品列表数据{}", allGoodsIds);
        List<Integer> goodsIds = allGoodsIds.subList(offset, offset + size);
        log.debug("全部商品列表数据，分页{}", goodsIds);
        goodsIdsRList.addAll(allGoodsIds);
        log.debug("重建商品列表缓存 cacheKey={} value={}", GOODS_INFO_ALL, allGoodsIds);
        return getGoodsByIds(goodsIds);
    }

}
